#include <linux/err.h>
#include <linux/fs2.h>
#include <linux/fdtable.h>
#include <linux/sched.h>

struct cred {

};

static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt,
					int flags, struct file *f,
					int (*open)(struct inode *, struct file *),
					const struct cred *cred)
{
	struct inode *inode;
	int error;

	printf("this is %s(): %d\r\n", __func__, __LINE__);

	f->f_flags = flags;
	// f->f_mode = (__force fmode_t)((flags+1) & O_ACCMODE) | FMODE_LSEEK |
	// 			FMODE_PREAD | FMODE_PWRITE;
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	inode = dentry->d_inode;
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	// if (f->f_mode & FMODE_WRITE) {
	// 	error = __get_file_write_access(inode, mnt);
	// 	if (error)
	// 		goto cleanup_file;
	// 	if (!special_file(inode->i_mode))
	// 		file_take_write(f);
	// }

	f->f_mapping = inode->i_mapping;
	f->f_path.dentry = dentry;
	f->f_path.mnt = mnt;
	f->f_pos = 0;
	// f->f_op = fops_get(inode->i_fop);
	f->f_op = inode->i_fop;
	// file_move(f, &inode->i_sb->s_files);
	printf("this is %s(): %d\r\n", __func__, __LINE__);

	// error = security_dentry_open(f, cred);
	// if (error)
		// goto cleanup_all;

	if (!open && f->f_op)
		open = f->f_op->open;
	if (open) {
		printf("this is %s(): %d\r\n", __func__, __LINE__);
		error = open(inode, f);
		if (error)
			goto cleanup_all;
	}
	printf("this is %s(): %d\r\n", __func__, __LINE__);

	// f->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);

	// file_ra_state_init(&f->f_ra, f->f_mapping->host->i_mapping);

	/* NB: we're sure to have correct a_ops only after f_op->open */
	// if (f->f_flags & O_DIRECT) {
	// 	if (!f->f_mapping->a_ops ||
	// 	    ((!f->f_mapping->a_ops->direct_IO) &&
	// 	    (!f->f_mapping->a_ops->get_xip_mem))) {
	// 		fput(f);
	// 		f = ERR_PTR(-EINVAL);
	// 	}
	// }

	printf("this is %s(): %d\r\n", __func__, __LINE__);
	return f;

cleanup_all:
	printf("this is %s(): %d\r\n", __func__, __LINE__);
// 	fops_put(f->f_op);
// 	if (f->f_mode & FMODE_WRITE) {
// 		put_write_access(inode);
// 		if (!special_file(inode->i_mode)) {
// 			/*
// 			 * We don't consider this a real
// 			 * mnt_want/drop_write() pair
// 			 * because it all happenend right
// 			 * here, so just reset the state.
// 			 */
// 			file_reset_write(f);
// 			mnt_drop_write(mnt);
// 		}
// 	}
// 	file_kill(f);
	f->f_path.dentry = NULL;
	f->f_path.mnt = NULL;
// cleanup_file:
// 	put_filp(f);
// 	dput(dentry);
// 	mntput(mnt);
	return ERR_PTR(error);
}

struct file *nameidata_to_filp(struct nameidata *nd, int flags)
{
	// const struct cred *cred = current_cred();
	const struct cred *cred = NULL;
	struct file *filp;

	/* Pick up the filp from the open intent */
	filp = nd->intent.open.file;
	/* Has the filesystem initialised the file for us? */
	// if (filp->f_path.dentry == NULL) {
		printf("this is %s(): %d\r\n", __func__, __LINE__);
		filp = __dentry_open(nd->path.dentry, nd->path.mnt, flags, filp,
				     NULL, cred);
	// }
	// else {
		// printf("this is %s(): %d\r\n", __func__, __LINE__);
		// path_put(&nd->path);
	// }
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	return filp;
}

void fd_install(unsigned int fd, struct file *file)
{
	struct files_struct *files = current->files;
	struct fdtable *fdt;

	printf("this is %s(): %d\r\n", __func__, __LINE__);

	// spin_lock(&files->file_lock);
	fdt = files_fdtable(files);
	// BUG_ON(fdt->fd[fd] != NULL);
	rcu_assign_pointer(fdt->fd[fd], file);
	// spin_unlock(&files->file_lock);

	printf("this is %s(): %d\r\n", __func__, __LINE__);
}

long do_sys_open(int dfd, const char *filename, int flags, int mode)
{
	printf("this is %s(): %d\r\n", __func__, __LINE__);
#if 0
	char *tmp = getname(filename);
	int fd = PTR_ERR(tmp);

	if (!IS_ERR(tmp)) {
		fd = get_unused_fd_flags(flags);
		if (fd >= 0) {
			struct file *f = do_filp_open(dfd, tmp, flags, mode, 0);
			if (IS_ERR(f)) {
				put_unused_fd(fd);
				fd = PTR_ERR(f);
			} else {
				fsnotify_open(f->f_path.dentry);
				fd_install(fd, f);
			}
		}
		putname(tmp);
	}
	return fd;
#else
	static int fd = 0;

	if (fd >= 0) {
		printf("this is %s(): %d\r\n", __func__, __LINE__);
		struct file *f = do_filp_open(dfd, filename, flags, mode, 0);
		if (IS_ERR(f)) {
			printf("this is %s(): %d\r\n", __func__, __LINE__);
			// put_unused_fd(fd);
			fd = PTR_ERR(f);
		} else {
			printf("this is %s(): %d\r\n", __func__, __LINE__);
			// fsnotify_open(f->f_path.dentry);
			fd_install(fd, f);
			printf("this is %s(): %d\r\n", __func__, __LINE__);
			// fd++;
		}
	}

	printf("this is %s(): %d >>> fd = %d\r\n", __func__, __LINE__, fd);
	return fd++;
#endif
	// struct file *f = do_filp_open(dfd, filename, flags, mode, 0);
	// printf("this is %s(): %d\r\n", __func__, __LINE__);
	// return f;
}

int sys_chdir(const char *filename)
{
	struct path path;
	int error;

	printf("this is %s(): %d\r\n", __func__, __LINE__);

	error = user_path_dir(filename, &path);
	if (error)
		goto out;
	printf("this is %s(): %d\r\n", __func__, __LINE__);

	// error = inode_permission(path.dentry->d_inode, MAY_EXEC | MAY_ACCESS);
	// if (error)
	// 	goto dput_and_out;
	printf("this is %s(): %d >>> path.mnt->mnt_root->d_name.name = %s\r\n", __func__, __LINE__, path.mnt->mnt_root->d_name.name);
	printf("this is %s(): %d >>> path.mnt->mnt_mountpoint->d_name.name = %s\r\n", __func__, __LINE__, path.mnt->mnt_mountpoint->d_name.name);
	printf("this is %s(): %d >>> path.dentry->d_name.name = %s\r\n", __func__, __LINE__, path.dentry->d_name.name);

	printf("this is %s(): %d >>> path.mnt->mnt_sb->s_type->name = %s\r\n", __func__, __LINE__, path.mnt->mnt_sb->s_type->name);
	printf("this is %s(): %d >>> path.mnt->mnt_parent->mnt_sb->s_type->name = %s\r\n", __func__, __LINE__, path.mnt->mnt_parent->mnt_sb->s_type->name);

	set_fs_pwd(current->fs, &path);

dput_and_out:
	// path_put(&path);
out:
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	return error;
}

long sys_open(const char *filename, int flags, int mode)
{
	long ret;

	// if (force_o_largefile())
	// 	flags |= O_LARGEFILE;

	ret = do_sys_open(AT_FDCWD, filename, flags, mode);
	/* avoid REGPARM breakage on x86: */
	// asmlinkage_protect(3, ret, filename, flags, mode);
	return ret;
}

int generic_file_open(struct inode * inode, struct file * filp)
{
	if (!(filp->f_flags & O_LARGEFILE) && i_size_read(inode) > MAX_NON_LFS)
		return -EOVERFLOW;
	return 0;
}
